package models

import (
	"3rd/mongo"
	"3rd/utils"
	goerrors "github.com/go-errors/errors"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
	"time"
)

type Order struct {
	OrderID bson.ObjectId `bson:"_id" json:"orderID,omitempty"`

	Sn string `bson:"sn" json:"sn"`

	StationProfile  `bson:"station" json:"station,omitempty"`
	DemanderProfile `bson:"demander" json:"demander,omitempty"`

	UnitPrice    float64 `bson:"unitPrice" json:"unitPrice" valid:"Required"`
	WaterQuality string  `bson:"waterQuality" json:"waterQuality" valid:"Required"`

	Total         int     `bson:"total" json:"total" valid:"Required"`
	Remain        int     `bson:"remain" json:"remain"`
	SentBuckets   int     `bson:"sentBuckets" json:"sentBuckets"`
	ReturnBotles  int     `bson:"returnBotles" json:"returnBotles"`
	RemainBottles int     `bson:"remainBottles" json:"remainBottles"`
	CallTs        []int64 `bson:"callTs" json:"callTs"`
	SentTs        int64   `bson:"sentTs" json:"sentTs"`
	State         int     `bson:"state" json:"state"`
	Postman       Postman `bson:"postman" json:"postman"`
	Type          int     `bson:"type" json:"type"`
	Comment       string  `bson:"comment" json:"comment"`
	Created       int64   `bson:"created" json:"created"`
}

func AddOrder(sn string) error {

	loc, err := time.LoadLocation("Local")
	if err != nil {
		return goerrors.New(err)
	}
	now := time.Now()
	year, month, day := now.Date()
	begin := time.Date(year, month, day, 6, 0, 0, 0, loc).Unix()
	end := time.Date(year, month, day, 20, 0, 0, 0, loc).Unix()

	var contract Contract
	if err = GetBindedContract(sn, &contract); err != nil {
		return goerrors.New(err)
	}

	return mongo.DO(db_order, col_order, func(c *mgo.Collection) (err error) {
		// had order already created which created time between begin and end && state not equal COMPLETE
		query := bson.M{
			"sn":      sn,
			"created": bson.M{"$gte": begin, "$lt": end},
			//"state":       bson.M{"$ne": COMPLETE},
			"station.qid": contract.Station.Qid,
			"$or": []bson.M{bson.M{"state": NEW}, bson.M{"state": ACCECPT}},
		}
		change := mgo.Change{
             Update: bson.M{"$push": bson.M{"callTs": now.Unix()}},
             ReturnNew: true,
     	}
		var order Order
		_, err = c.Find(query).Apply(change, &order)
		if err != nil {
		//if err = c.Update(query, bson.M{"$push": bson.M{"callTs": now.Unix()}}); err != nil {
			// TODO: guarantee session in safe mode
			if err == mgo.ErrNotFound {
				order.OrderID = bson.NewObjectId()
				order.Sn = sn
				order.Created = now.Unix()
				order.StationProfile = *contract.Station
				order.DemanderProfile = contract.Customer.DemanderProfile
				order.UnitPrice = contract.UnitPrice
				order.WaterQuality = contract.WaterQuality
				order.Total = contract.Total
				order.Remain = contract.Remain
				order.CallTs = []int64{order.Created}
				order.State = NEW
				if err = c.Insert(order); err != nil {
					return goerrors.New(err)
				}
				Log.Alert("%s:%s call for water", sn, order.DemanderProfile.Company)
				PushMsg2MasterAsync(order.StationProfile.Qid, map[string]interface{}{
					"type":         NOTIFY_CALL,
					"address":      order.DemanderProfile.Address,
					"buckets":      order.DemanderProfile.UnitDelivery,
					"timeline":     order.Tolerance,
					"waterquality": order.WaterQuality,
				})
				return nil
			}
			return goerrors.New(err)
		}
		notice := map[string]interface{}{
			"type":         NOTIFY_REMAINDER,
			"address":      order.DemanderProfile.Address,
			"buckets":      order.DemanderProfile.UnitDelivery,
			"timeline":     order.Tolerance,
			"waterquality": order.WaterQuality,
		}
		PushMsg2MasterAsync(order.StationProfile.Qid, notice)
		if order.Type == NOTIFY_REMAINDER && order.Postman.Qid != "" && order.Postman.Qid != order.StationProfile.Qid {
			PushMsg2PostmanAsync(order.Postman.Qid , notice)
		}

		Log.Alert("%s:%s crazy for water", sn, order.Company)

		return nil
	})
}

func AddOrderWithCount(sn string, count int) error {

	loc, err := time.LoadLocation("Local")
	if err != nil {
		return goerrors.New(err)
	}
	now := time.Now()
	year, month, day := now.Date()
	begin := time.Date(year, month, day, 6, 0, 0, 0, loc).Unix()
	end := time.Date(year, month, day, 20, 0, 0, 0, loc).Unix()

	var contract Contract
	if err = GetBindedContract(sn, &contract); err != nil {
		return goerrors.New(err)
	}

	return mongo.DO(db_order, col_order, func(c *mgo.Collection) (err error) {
		// had order already created which created time between begin and end && state not equal COMPLETE
		query := bson.M{
			"sn":      sn,
			"created": bson.M{"$gte": begin, "$lt": end},
			//"state":       bson.M{"$ne": COMPLETE},
			"station.qid": contract.Station.Qid,
			"$or": []bson.M{bson.M{"state": NEW}, bson.M{"state": ACCECPT}},
		}
		change := mgo.Change{
             Update: bson.M{"$push": bson.M{"callTs": now.Unix()}},
             ReturnNew: true,
     	}
		var order Order
		_, err = c.Find(query).Apply(change, &order)
		if err != nil {
		//if err = c.Update(query, bson.M{"$push": bson.M{"callTs": now.Unix()}}); err != nil {
			// TODO: guarantee session in safe mode
			if err == mgo.ErrNotFound {
				order.OrderID = bson.NewObjectId()
				order.Sn = sn
				order.Created = now.Unix()
				order.StationProfile = *contract.Station
				order.DemanderProfile = contract.Customer.DemanderProfile
				order.UnitPrice = contract.UnitPrice
				order.WaterQuality = contract.WaterQuality
				order.Total = contract.Total
				order.Remain = contract.Remain
				order.CallTs = []int64{order.Created}
				order.State = NEW
				if err = c.Insert(order); err != nil {
					return goerrors.New(err)
				}
				Log.Alert("%s:%s call for water", sn, order.DemanderProfile.Company)
				PushMsg2MasterAsync(order.StationProfile.Qid, map[string]interface{}{
					"type":         NOTIFY_CALL,
					"address":      order.DemanderProfile.Address,
					"buckets":      count,
					"timeline":     order.Tolerance,
					"waterquality": order.WaterQuality,
				})
				return nil
			}
			return goerrors.New(err)
		}
		notice := map[string]interface{}{
			"type":         NOTIFY_REMAINDER,
			"address":      order.DemanderProfile.Address,
			"buckets":      count,
			"timeline":     order.Tolerance,
			"waterquality": order.WaterQuality,
		}
		PushMsg2MasterAsync(order.StationProfile.Qid, notice)
		if order.Type == NOTIFY_REMAINDER && order.Postman.Qid != "" && order.Postman.Qid != order.StationProfile.Qid {
			PushMsg2PostmanAsync(order.Postman.Qid , notice)
		}

		Log.Alert("%s:%s crazy for water", sn, order.Company)

		return nil
	})
}

func AcceptOrder(qid, orderID string, postman Postman, postType int) error {
	return mongo.DO(db_order, col_order, func(c *mgo.Collection) (err error) {
		query := bson.M{
			"_id":         bson.ObjectIdHex(orderID),
			"station.qid": qid,
			"state":       NEW,
		}
		change := mgo.Change{
             Update: bson.M{
			"$set": bson.M{"postman": postman, "type": postType},
			"$inc": bson.M{"state": ACCECPT - NEW},
			},
             ReturnNew: true,
     	}
		
		var order Order
		_, err = c.Find(query).Apply(change, &order)
		if err != nil {
			return goerrors.New(err)
		}

		if postType != SELF && postman.Qid != "" {
							PushMsg2PostmanAsync(postman.Qid, map[string]interface{}{
								"type": NOTIFY_CALL,
								"address":order.DemanderProfile.Address,
								"buckets":order.DemanderProfile.UnitDelivery,
								"timeline":order.Tolerance,
								"waterquality":order.WaterQuality,
							})
		}
		return nil
	})
}

func CompleteOrder(orderID, sn string, sentBuckets, returnBotles int, comment string) error {
	return mongo.DO(db_order, col_order, func(c *mgo.Collection) (err error) {
		query := bson.M{
			"_id": bson.ObjectIdHex(orderID),
			//"station.qid": qid,
			"state": ACCECPT,
		}
		change := mgo.Change{
			  Update: bson.M{"$set": bson.M{
				"state":        COMPLETE,
				"sentTs":       time.Now().Unix(),
				"sentBuckets":  sentBuckets,
				"returnBotles": returnBotles,
				"comment":      comment},
				"$inc": bson.M{"remain": -sentBuckets, "remainBottles": sentBuckets - returnBotles},
				},
             ReturnNew: true,
		}
		
		var order Order
		_, err = c.Find(query).Apply(change, &order)
		if err != nil {
			return goerrors.New(err)
		}
		if order.Postman.Qid != order.StationProfile.Qid {
							PushMsg2MasterAsync(order.StationProfile.Qid, map[string]interface{}{
							"type": NOTIFY_COMPLETEORDER,
							"address":order.DemanderProfile.Address,
						})	
		}

		if err := IncStock(sn, -sentBuckets, sentBuckets-returnBotles); err != nil {
			return goerrors.New(err)
		}
		return nil
	})
}

func GetOrders(qid, stationID string, count, state int, postman, customer []string,
	cursor, cursorEnd int64, orders *[]Order) error {
	states := utils.BitArray(state)
	return mongo.DO(db_order, col_order, func(c *mgo.Collection) (err error) {
		query := bson.M{
			//"station._id": stationID,
			"station.qid": qid,
			"state":       bson.M{"$in": states},
			"created":     bson.M{"$lt": cursor, "$gte": cursorEnd},
		}
			sort := "-created"
		if cursor == 0 {
			sort = "created"
			query["created"] = bson.M{"$gte": cursorEnd}
		}
		if postman != nil && len(postman) > 0 {
			query["postman.name"] = bson.M{"$in": postman}
		}
		if customer != nil && len(customer) > 0 {
			query["demander.company"] = bson.M{"$in": customer}
		}

		if err := c.Find(query).Sort(sort).Limit(count).All(orders); err != nil {
			return goerrors.New(err)
		}
		return nil
	})
}

func AllOrders(count, state int, postman, customer []string,
	cursor, cursorEnd int64, orders *[]Order) error {
	states := utils.BitArray(state)
	return mongo.DO(db_order, col_order, func(c *mgo.Collection) (err error) {
		query := bson.M{
			"state":       bson.M{"$in": states},
			"created":     bson.M{"$lt": cursor, "$gte": cursorEnd},
		}
		sort := "-created"
		if cursor == 0 {
			sort = "created"
			query["created"] = bson.M{"$gte": cursorEnd}
		}
		if postman != nil && len(postman) > 0 {
			query["postman.name"] = bson.M{"$in": postman}
		}
		if customer != nil && len(customer) > 0 {
			query["demander.company"] = bson.M{"$in": customer}
		}

		if err := c.Find(query).Sort(sort).Limit(count).All(orders); err != nil {
			return goerrors.New(err)
		}
		return nil
	})
}

func GetPostmanOrders(qid string, count int, cursor int64, orders *[]Order) error {
	return mongo.DO(db_order, col_order, func(c *mgo.Collection) (err error) {
		query := bson.M{
			"postman._id": qid,
			"state":       ACCECPT,
			"created":     bson.M{"$lt": cursor},
		}
		if err := c.Find(query).Sort("-created").Limit(count).All(orders); err != nil {
			return goerrors.New(err)
		}
		return nil
	})
}
